package client

import (
	"context"
	"errors"
	"github.com/ethereum/go-ethereum/accounts/abi/bind"
	"github.com/ethereum/go-ethereum/common"
	"github.com/ethereum/go-ethereum/core/types"
	"github.com/x-mod/routine"
	"go.uber.org/zap"
	"math/big"
	"shendaNFT/pkg/contract/shendaNFT"
	"shendaNFT/pkg/model"
	"time"
)

const timeout = time.Second * 35

// deployTask 部署合约并行任务
type deployTask struct {
	logger *zap.Logger
	client *EthClient
	opts   *bind.TransactOpts
	param  model.DeployParam
	result *model.DeployResult
}

// DeployBatch 批量部署
func (c *EthClient) DeployBatch(param model.DeployBatchParam) ([]*model.DeployResult, error) {
	ctx := context.Background()
	// 获取账户交易Nonce值
	transactOpts, err := c.NewTransactOpts()
	if err != nil {
		return nil, err
	}
	opts, err := bind.NewKeyedTransactorWithChainID(c.privateKey, c.chainID)
	if err != nil {
		return nil, err
	}
	nonce, err := c.client.PendingNonceAt(ctx, opts.From)
	if err != nil {
		return nil, err
	}
	transactOpts.Nonce = big.NewInt(int64(nonce))
	// 构建部署合约的并行任务
	count := len(param.Items)
	execs := make([]routine.Executor, count)
	tasks := make([]*deployTask, count)
	results := make([]*model.DeployResult, count)
	for i := 0; i < count; i++ {
		nonce := new(big.Int)
		nonce = nonce.Add(transactOpts.Nonce, big.NewInt(int64(i)))
		opts := &bind.TransactOpts{
			From:     transactOpts.From,
			Nonce:    nonce,
			Signer:   transactOpts.Signer,
			Value:    transactOpts.Value,
			GasPrice: transactOpts.GasPrice,
			GasLimit: transactOpts.GasLimit,
			Context:  transactOpts.Context,
		}
		params := model.DeployParam{
			Owner:   param.Items[i].Owner,
			Name:    param.Items[i].Name,
			BaseURI: param.Items[i].BaseURI,
		}
		results[i] = &model.DeployResult{}
		task := &deployTask{logger: c.logger, client: c, opts: opts, param: params, result: results[i]}
		tasks[i] = task
		execs[i] = routine.ExecutorFunc(task.Deploy)
	}
	// 执行并行任务
	parallel := routine.Parallel(execs...)
	err = parallel.Execute(ctx)
	if err != nil {
		c.logger.Sugar().Errorw("routine error :", "error", err.Error())
		return nil, err
	}
	return results, nil
}

// Deploy 部署合约
func (t *deployTask) Deploy(ctx context.Context) error {
	// 发送部署合约交易
	address, tx, _, err := shendaNFT.DeployShenDaNFT(t.opts, t.client.client, t.param.Name, t.param.Name, t.param.BaseURI, common.HexToAddress(t.param.Owner))
	if err != nil {
		return err
	}
	t.result.Address = address.String()
	// 等待交易结果
	transaction, err := t.client.waitForResult(tx.Hash())
	if err != nil {
		return err
	}
	t.result.Transaction = *transaction
	return nil
}

// Deploy 部署合约
func (c *EthClient) Deploy(param model.DeployParam) (*model.DeployResult, error) {
	// 获取账户交易Nonce值
	opts, err := c.NewTransactOpts()
	if err != nil {
		return nil, nil
	}
	// 发送部署合约交易
	address, tx, _, err := shendaNFT.DeployShenDaNFT(opts, c.client, param.Name, param.Name, param.BaseURI, common.HexToAddress(param.Owner))
	if err != nil {
		return nil, err
	}
	// 等待交易确认
	result, err := c.waitForResult(tx.Hash())
	if err != nil {
		return nil, err
	}
	return &model.DeployResult{
		Address:     address.String(),
		Transaction: *result,
	}, nil
}

// mintTask 铸造方法并行任务
type mintTask struct {
	logger   *zap.Logger
	client   *EthClient
	contract *shendaNFT.ShenDaNFT
	opts     *bind.TransactOpts

	param  model.MintBatchParamItem
	result *model.MintResult
}

// Mint 铸造方法
func (t *mintTask) Mint(ctx context.Context) error {
	// 发送铸造交易
	tx, err := t.contract.Mint(t.opts, common.HexToAddress(t.param.Owner), t.param.TokenURI)
	if err != nil {
		t.logger.Sugar().Errorw("mint error :", "error", err.Error(), "owners", t.param.Owner, "tokenURI", t.param.TokenURI)
		return err
	}
	// 等待交易结果
	transaction, err := t.client.waitForResult(tx.Hash())
	if err != nil {
		return err
	}
	t.result.Transaction = *transaction
	return nil
}

func (c *EthClient) MintBatch(param model.MintBatchParam) (results []*model.MintResult, err error) {
	c.lock.Lock()
	defer c.lock.Unlock()
	// 绑定合约
	contract, err := shendaNFT.NewShenDaNFT(common.HexToAddress(param.Contract), c.client)
	if err != nil {
		c.logger.Sugar().Errorw("new contract error :", "error", err.Error())
		return nil, err
	}
	// 交易参数
	opts, err := c.NewTransactOpts()
	opts.GasLimit = 400000
	if err != nil {
		c.logger.Sugar().Errorw("contract GetCounter error :", "error", err.Error())
		return nil, err
	}
	// 获取tokenID计数
	counter, err := contract.GetCounter(&bind.CallOpts{Pending: true})
	if err != nil {
		return nil, err
	}
	count := len(param.Items)
	results = make([]*model.MintResult, count)
	for i := 0; i < count; i++ {
		tokenID := new(big.Int)
		tokenID.Add(counter, big.NewInt(int64(i)))
		// 发送铸造交易
		tx, err := contract.Mint(opts, common.HexToAddress(param.Items[i].Owner), param.Items[i].TokenURI)
		if err != nil {
			c.logger.Sugar().Errorw("mint error :", "error", err.Error(), "owners", param.Items[i].Owner, "tokenURI", param.Items[i].TokenURI)
			return results, err
		}
		results[i] = &model.MintResult{
			TokenID:     tokenID.Uint64(),
			Transaction: model.Transaction{TxHash: tx.Hash().String()},
		}
	}
	return results, nil
}

// MintBatchSync 同步方法保留
func (c *EthClient) MintBatchSync(param model.MintBatchParam) (results []*model.MintResult, err error) {
	ctx := context.Background()
	// 绑定合约
	contract, err := shendaNFT.NewShenDaNFT(common.HexToAddress(param.Contract), c.client)
	if err != nil {
		c.logger.Sugar().Errorw("new contract error :", "error", err.Error())
		return nil, err
	}
	// 交易参数
	transactOpts, err := c.NewTransactOpts()
	if err != nil {
		c.logger.Sugar().Errorw("contract GetCounter error :", "error", err.Error())
		return nil, err
	}
	// 获取tokenID计数
	counter, err := contract.GetCounter(&bind.CallOpts{Pending: true})
	if err != nil {
		return nil, err
	}
	count := len(param.Items)
	execs := make([]routine.Executor, count)
	tasks := make([]*mintTask, count)
	results = make([]*model.MintResult, count)
	for i := 0; i < count; i++ {
		nonce := new(big.Int)
		nonce = nonce.Add(transactOpts.Nonce, big.NewInt(int64(i)))
		opts := &bind.TransactOpts{
			From:     transactOpts.From,
			Nonce:    nonce,
			Signer:   transactOpts.Signer,
			Value:    transactOpts.Value,
			GasPrice: transactOpts.GasPrice,
			GasLimit: 400000,
			Context:  ctx,
		}
		params := model.MintBatchParamItem{
			Owner:    param.Items[i].Owner,
			TokenURI: param.Items[i].TokenURI,
		}
		tokenID := new(big.Int)
		tokenID.Add(counter, big.NewInt(int64(i)))
		results[i] = &model.MintResult{
			TokenID: tokenID.Uint64(),
		}
		task := &mintTask{logger: c.logger, client: c, contract: contract, opts: opts, param: params, result: results[i]}
		tasks[i] = task
		execs[i] = routine.ExecutorFunc(task.Mint)
	}
	// 等待结果返回
	parallel := routine.Parallel(execs...)
	err = parallel.Execute(ctx)
	if err != nil {
		c.logger.Sugar().Errorw("routine error :", "error", err.Error())
		return nil, err
	}
	return results, nil
}

// TransferFrom 转移NFT所有权
func (c *EthClient) TransferFrom(contract, from, to common.Address, tokenID *big.Int) (*model.Transaction, error) {
	// 交易参数
	opts, err := bind.NewKeyedTransactorWithChainID(c.privateKey, c.chainID)
	if err != nil {
		return nil, err
	}
	// 绑定合约
	shenDaNFT, err := shendaNFT.NewShenDaNFT(contract, c.client)
	if err != nil {
		return nil, err
	}
	// 调用合约转移方法
	tx, err := shenDaNFT.SafeTransferFrom(opts, from, to, tokenID)
	if err != nil {
		return nil, err
	}
	return c.waitForResult(tx.Hash())
}

type transferFromTask struct {
	logger   *zap.Logger
	client   *EthClient
	contract *shendaNFT.ShenDaNFT
	opts     *bind.TransactOpts

	param  model.TransferFromParams
	result *model.Transaction
}

// TransferFrom 转移NFT所有权并行任务
func (t *transferFromTask) TransferFrom(ctx context.Context) error {
	// 绑定合约
	shenDaNFT, err := shendaNFT.NewShenDaNFT(common.HexToAddress(t.param.Contract), t.client.client)
	if err != nil {
		return err
	}
	// 调用合约转移方法
	tx, err := shenDaNFT.SafeTransferFrom(t.opts, common.HexToAddress(t.param.From), common.HexToAddress(t.param.To), big.NewInt(int64(t.param.TokenID)))
	if err != nil {
		return err
	}
	// 等待交易结果
	result, err := t.client.waitForResult(tx.Hash())
	if err != nil {
		return err
	}
	t.result.TxHash = result.TxHash
	t.result.Timestamp = result.Timestamp
	t.result.BlockNumber = result.BlockNumber
	t.result.Status = result.Status
	t.result.GasUsed = result.GasUsed
	return nil
}

// TransferFromBatch 转移NFT所有权
func (c *EthClient) TransferFromBatch(params []model.TransferFromParams) ([]*model.Transaction, []string) {
	count := len(params)
	ctx := context.Background()
	// 交易参数
	opts, err := c.NewTransactOpts()
	if err != nil {
		return nil, nil
	}
	nonceAt, err := c.client.PendingNonceAt(ctx, opts.From)
	if err != nil {
		return nil, nil
	}
	// 构建部署合约的并行任务
	execs := make([]routine.Executor, count)
	tasks := make([]*transferFromTask, count)
	results := make([]*model.Transaction, count)

	for i := 0; i < count; i++ {
		nonce := new(big.Int)
		nonce = nonce.Add(big.NewInt(int64(nonceAt)), big.NewInt(int64(i)))
		param := params[i]
		results[i] = &model.Transaction{}
		task := &transferFromTask{logger: c.logger, client: c, opts: &bind.TransactOpts{
			From:     opts.From,
			Nonce:    nonce,
			Signer:   opts.Signer,
			GasPrice: opts.GasPrice,
			GasLimit: opts.GasLimit,
			Context:  opts.Context,
		}, param: param, result: results[i]}
		tasks[i] = task
		execs[i] = routine.ExecutorFunc(task.TransferFrom)
	}

	// 执行并行任务
	parallel := Parallel(execs...)
	err = parallel.Execute(ctx)
	if err != nil {
		c.logger.Sugar().Errorw("routine error :", "error", err.Error())
		return nil, parallel.errs
	}
	return results, parallel.errs
}

// Burn 销毁NFT
func (c *EthClient) Burn(contract common.Address, tokenID *big.Int) (*model.Transaction, error) {
	// 交易参数
	opts, err := c.NewTransactOpts()
	if err != nil {
		return nil, err
	}
	// 绑定合约
	shenDaNFT, err := shendaNFT.NewShenDaNFT(contract, c.client)
	if err != nil {
		return nil, err
	}
	// 调用合约销毁方法
	tx, err := shenDaNFT.Burn(opts, tokenID)
	if err != nil {
		return nil, err
	}
	return c.waitForResult(tx.Hash())
}

// waitForResult 监听新区块检查交易是否被确认
func (c *EthClient) waitForResult(txHash common.Hash) (*model.Transaction, error) {
	ctx := context.Background()
	headerChan := make(chan *types.Header)
	subscribeNewHead, err := c.client.SubscribeNewHead(ctx, headerChan)
	if err != nil {
		c.logger.Sugar().Errorw("subscribeNewHead error :", "error", err.Error())
		return nil, err
	}
	defer subscribeNewHead.Unsubscribe()
	for {
		select {
		case err = <-subscribeNewHead.Err():
			return nil, err
		case head := <-headerChan:
			_, isPending, err := c.client.TransactionByHash(ctx, txHash)
			if err != nil {
				return nil, err
			}
			if !isPending {
				receipt, err := c.client.TransactionReceipt(ctx, txHash)
				if err != nil {
					return nil, err
				}
				transaction := &model.Transaction{
					TxHash:      receipt.TxHash.String(),
					Status:      receipt.Status,
					BlockNumber: receipt.BlockNumber.Uint64(),
					Timestamp:   head.Time,
					GasUsed:     receipt.GasUsed,
				}
				return transaction, nil
			}
		case <-time.After(timeout):
			return nil, errors.New("time out")
		}
	}
}

// TotalSupply 查询合约中总的NFT数量
func (c *EthClient) TotalSupply(contract common.Address) (*big.Int, error) {
	shenDaNFT, err := shendaNFT.NewShenDaNFT(contract, c.client)
	if err != nil {
		return nil, err
	}
	supply, err := shenDaNFT.TotalSupply(&bind.CallOpts{Pending: true})
	if err != nil {
		return nil, err
	}
	return supply, nil
}

// BalanceOf 查询某个账户的代币余额
func (c *EthClient) BalanceOf(contract, owner common.Address) *big.Int {
	shenDaNFT, err := shendaNFT.NewShenDaNFT(contract, c.client)
	if err != nil {
		return nil
	}
	balance, err := shenDaNFT.BalanceOf(&bind.CallOpts{Pending: true}, owner)
	if err != nil {
		return nil
	}
	return balance
}

// OwnerOf 查看NFT拥有者
func (c *EthClient) OwnerOf(contract common.Address, tokenID *big.Int) (common.Address, error) {
	shenDaNFT, err := shendaNFT.NewShenDaNFT(contract, c.client)
	if err != nil {
		return common.Address{}, err
	}
	owner, err := shenDaNFT.OwnerOf(&bind.CallOpts{Pending: true}, tokenID)
	if err != nil {
		return common.Address{}, err
	}
	return owner, nil
}
